#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <boost/thread.hpp>
#include "TestflowMining.h"
#include "Symtab.h"
#include "Ast.h"
//#include "MiningWrapper.h"
 extern "C" {	
#include "MacString.h"
}

using namespace std;

N32 TimestampType::checkValue(char *operand, char *table_name)
{
	long temp;
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{       
		temp = atol(operand);
	
		if(temp == 0 && !(operand[0] == '0'))
		return 0;
		if((temp < getMinValue()) || (temp > getMaxValue()))
		{
			cout << "\nOperand " << operand << " not a valid Timestamp Type." << endl;
			return 0;
		}
	}	
	return 1;	
}

N32 PortnumType::checkValue(char *operand, char *table_name)
{
	int type_operand;
	int return_value = 1;	
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	
	if(return_value == 0 || table_name == NULL)
	{
		long temp = atol(operand);
		if(temp == 0 && !(operand[0] == '0'))
		return 0;
		if((temp < getMinValue()) || (temp > getMaxValue()))
		{
			cout << "\nOperand " << operand << " not a valid Port Number." << endl;
			return 0;
		}
	}
		
	return 1;
}

N32 PacketType::checkValue(char *operand, char *table_name)
{
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{
		long temp = atol(operand);
		if(temp == 0 && !(operand[0] == '0'))
		return 0;
		if((temp < getMinValue()) || (temp > getMaxValue()))
		{
			cout << "\nOperand " << operand << " not a valid Packet Type." << endl;
			return 0;
		}
	}		
	return 1;
}

N32 ByteType::checkValue(char *operand, char *table_name)
{
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{	
		long temp = atol(operand);
		if(temp == 0 && !(operand[0] == '0'))
		return 0;
		if((temp < getMinValue()) || (temp > getMaxValue()))
		{
			cout << "\nOperand " << operand << " not a Byte Type." << endl;
			return 0;
		}
	}		
return 1;
}

N32 AppType::checkValue(char *operand, char *table_name)
{
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{
		long temp = atol(operand);
		if(temp == 0 && !(operand[0] == '0'))
		return 0;
		if((temp < getMinValue()) || (temp > getMaxValue()))
		{
			cout << "\nOperand " << operand << " not a valid App ID." << endl;
			return 0;
		}
	}
	return 1;
}	
	
  


N32 IPv4Type::checkValue( char *operand, char *table_name)
{
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{
		struct in_addr addr;
		if(inet_pton(AF_INET, operand, &(addr.s_addr)) == 0)
		{
			cout << "\nOperand " << operand << " not a valid IPv4 address." << endl;
			return 0;
		}	
	}
	return 1;
}



N32 IPv4Type::makeMask(int mask)
{
	if((mask>32)||(mask == 0))
		mask = 32;
	return (32-mask);
}

IPv4Type &IPv4Type::operator=(char *ip)
{
	struct in_addr  addr;
	char *maskp = strchr(ip,'/');
	int mask;
	if(maskp){
		mask = int(maskp+1);
		inet_aton(ip, &addr);
	}else {
		mask = 32;
		inet_aton(ip, &addr);
	}
	mMask = makeMask(mask);
	mIP = addr.s_addr>>mMask;
	return *this;
}

N32 IPv6Type::checkValue(char *operand, char *table_name) 
{
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{
		struct in6_addr addrv6;
		if(inet_pton(AF_INET6, operand, &(addrv6.s6_addr)) == 0)
		{
			cout << "\nOperand " << operand << " not a valid IPv6 address." << endl;
			return 0;
		}
	}
	return 1;
}



N32 MACType::checkValue(char *operand, char *table_name)
{    
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{   
		pszMACAddress = operand;
		pbyAddress = MacAddrIntoByte(pszMACAddress, pbyAddress);
		if (pbyAddress == NULL)
		{
			cout << "\nOperand " << operand << " not a valid MAC address." << endl;
			return 0;
		}
	}
	return 1;
}


N32 FillerType::checkValue(char *operand, char *table_name)
{
	int i;
	
	int type_operand;
	int return_value = 1;
	//Check if operand is a column name
	if(table_name != NULL)
	{
		BaseMining *mining = BaseMining::getMiningInstance(table_name);
		//Check if table name is valid
		if(mining == NULL)
		{
			return_value = 0;
		}
		
		//Determine if the column name is correct and obtain the datatype of column(operand) if it is
		type_operand = mining->getType(operand);
		
		//Confirm if the column name is correct
		if(type_operand == -1)
		{
			//Column name is invalid.
			return_value = 0;
		}
		//Determine if the operand is of a compatible datatype
		
		if(type_operand != getType())
		{
			return_value = 0;
		}
	}
	if(return_value == 0 || table_name == NULL)
	{	
        FillerType::filler = operand;
		return 1;
	}
	return 1;
}

TestFlow::TestFlow()
{
	
	no_columns = 15;
	
	strcpy(columns[0].column_name, "mTimestamp");
	strcpy(columns[1].column_name, "mSrcMAC");
	strcpy(columns[2].column_name, "mDstMAC");
	strcpy(columns[3].column_name, "mSrcIPV4");
	strcpy(columns[4].column_name, "mDstIPV4");
	strcpy(columns[5].column_name, "mSrcIPV6");
	strcpy(columns[6].column_name, "mDstIPV6");
	strcpy(columns[7].column_name, "mSrcPort");
	strcpy(columns[8].column_name, "mDstPort");
	strcpy(columns[9].column_name,"mPacketsSrc2Dst");
	strcpy(columns[10].column_name, "mPacketsDst2Src");
	strcpy(columns[11].column_name, "mBytesSrc2Dst");
	strcpy(columns[12].column_name, "mBytesDst2Src");
	strcpy(columns[13].column_name, "app_id");
	strcpy(columns[14].column_name, "filler");
	columns[0].type = new TimestampType();
	columns[1].type = new MACType();
	columns[2].type = new MACType();
	columns[3].type = new IPv4Type();
	columns[4].type = new IPv4Type();
	columns[5].type = new IPv6Type();
	columns[6].type = new IPv6Type();
	columns[7].type = new PortnumType();
	columns[8].type = new PortnumType();
	columns[9].type = new PacketType();
	columns[10].type = new PacketType();
	columns[11].type = new ByteType();
	columns[12].type = new ByteType();
	columns[13].type = new AppType();
	columns[14].type = new FillerType();
}

TestFlow::~TestFlow()
{
	for(int i = 0; i < no_columns; i++)
	{
		delete columns[i].type;
	}
}

int TestFlow::verifyName(char *cname)
{
	int i;
	for(i = 0; i < 15; i++)
	{
		if(strcmp(cname, columns[i].column_name) == 0)
		{
			return i;
		}
	}
	return -1;
}

int TestFlow::getType(char *name)
{
	int i;
	i = verifyName(name);
	if(i == -1)
	{
		return -1;
	}
	return columns[i].type->getType();
}
char * TestFlow::verify(struct object_chain *ptr, bool proj, char *funcref)
{
	if(ptr == NULL)
	{
		return NULL;
	}
	if(verifyName(ptr->object_name) != -1)
	{
		if(proj)
			setProjection(ptr->object_name, funcref);
		return NULL;
	}
	return ptr->object_name;
}




/*bool *TestFlow::setCond(int optr, int total, int counter)
{
	int type = 2;
	long int r1 =-1, r2=-1;
	type = type + counter;
		if (addExpr(NULL, optr, r1, r2, NULL, type, total)==0)
			return 0;
	return 1; 
}*/

int TestFlow::setProjection(char *name, char *funcref)
{
	int cIndex;
	string column_name, function_reference;
	if(funcref == NULL)
	{
		column_name.assign(name);
		function_reference.assign("");
		projection.insert(pair<string, string>(column_name, function_reference));
		return 1;
	}
	if((strcasecmp(funcref, "MAX") == 0) ||	(strcasecmp(funcref, "MIN") == 0))
	{
		cIndex = verifyName(name);
		if(columns[cIndex].type->getOperations() & OP_RELATIONAL == 0)
		{
			cout << funcref << " is not allowed for column " << name << " in Testflow." << endl;
			return 0;
		}
		column_name.assign(name);
		function_reference.assign(funcref); 
		projection.insert(pair<string, string>(column_name, function_reference));
		return 1;
	}
	if((strcasecmp(funcref, "SUM") == 0) ||	(strcasecmp(funcref, "AVG") == 0))
	{
		cIndex = verifyName(name);
		if((columns[cIndex].type->getOperations() & OP_MATHEMATICAL) == 0)
		{
			cerr << funcref << " is not allowed for column " << name << " in Testflow." << endl;
			return 0;
		}
		column_name.assign(name);
		function_reference.assign(funcref); 
		projection.insert(pair<string, string>(column_name, function_reference));
		return 1;
	}
	column_name.assign(name);
	function_reference.assign(funcref);
	projection.insert(pair<string, string>(column_name, function_reference));
	return 1;	
		
}

void TestFlow::setProjectionsAll()
{
	int i;
	string column_name, function_reference;
	function_reference.assign("");
	for(i = 0; i < no_columns; i++)
	{
		column_name.assign(columns[i].column_name);
		projection.insert(pair<string, string>(column_name, function_reference));
	}
}

void TestFlow::displayProjection()
{
	multimap<string, string>::const_iterator projection_iterator;
	cout << "\nProjection name | Function Reference" << endl;
	cout << "--------------------------------------" << endl;
	for(projection_iterator = projection.begin(); projection_iterator != projection.end(); projection_iterator++)
	{
		cout << projection_iterator->first << "\t | " << projection_iterator->second << endl;
	}
}



void *TestFlow::setFilter1(char *name, MINEOPERATOR op, char *value, int total, int counter)
{
	void *temp = NULL;
	int oper;
	int i;
	if ((name != NULL) && (value != NULL))
	{
		i = verifyName(name);
		cout << "Filter: " << endl;
		cout << "Name: " << name << "	Operator: " << op << "	Value: " << value << endl;
		if(i == -1)
		{
			cout << name << " is not a valid column name." << endl;
			return temp;
		}
	
	switch(op)
	{
		case OP_NONE:
			oper = OP_NULL;
			break;
		case OP_EQ:
		case OP_NEQ:
			oper = OP_EQUALITY;
			break;
		case OP_LT:
		case OP_GT:
		case OP_LTE:
		case OP_GTE:
			oper = OP_RELATIONAL;
			break;
		case OP_PLUS:
		case OP_MINUS:
		case OP_DIVIDE:
		case OP_MULTIPLY:
		case OP_MODULUS:
			oper = OP_MATHEMATICAL;
			break;
		case OP_BAND:
		case OP_BOR:
		case OP_XOR:
		case OP_BNOT:
		case OP_LS:
		case OP_RS:
			oper = OP_BITWISE;
			break;
	}
			
	if((columns[i].type->getOperations() & oper) == 0)
	{
		switch (oper)
		{
			case OP_EQUALITY: 	
					cout << "Equality operation not supported." << endl;
					break;
			case OP_RELATIONAL:
					cout << "Relational operations not supported." << endl;
					break;
			case OP_MATHEMATICAL:
					cout << "Mathematical operations not supported." << endl;
					break;
			case OP_BITWISE:
					cout << "Bitwise operations not supported." << endl;
					break;
		}
		return temp;
	}
	if(columns[i].type->checkValue(value, "Testflow") == 0)
	{
		cout << value << " is invalid." << endl;
		return temp;
	}
	
	temp = (void *)value;
	return temp;
   }
   else 
	{
	switch (op)
		{
		case OP_AND:
		cout << "\nName: <Void>  Operator: AND  Value: <Void>" << endl;
		break;
		case OP_OR:
		cout << "\nName: <Void>  Operator: OR  Value: <Void>" << endl;
		break;
		case OP_NOT:
		cout << "\nName: <Void>  Operator: NOT  Value: <Void>" << endl;
		break;
		}
	} 	
	return temp;	
}

void *TestFlow::setFilter2(void *node, MINEOPERATOR op, char *value, int total, int counter )
{
	cout << "Filter: " << endl;
	cout << "Node: " << (char *)node << endl;
	cout << "Operator: " << op << endl;
	cout << "Value: " << value;
	return node;
}

void *TestFlow::setFilter3(char *name, MINEOPERATOR op, void *node, int total, int counter)
{
	int cIndex = verifyName(name);
	NODE *expr_node = (NODE *)node;
	if(cIndex == -1)
	{
		cerr << "File Name: TestflowMining.cpp Function Name: setFilter3 ";
		cerr << "Column " << name << " not found." << "Code should not get here!" << endl;
		exit(1);
	}
	if(expr_node->value != NULL)
	{
		if(expr_node->node_type == 0)
		{
			if(columns[cIndex].type->checkValue((char *)expr_node->value, "Testflow") == 0)
			{
				cerr << "ERROR: " << name << " and " << (char *)expr_node->value << " are incompatible." << endl;
				exit(1); //For now
			}
		}
		else
		{	
			if(columns[cIndex].type->checkValue(expr_node->value->name, "Testflow") == 0)
			{
				cerr << "ERROR: " << name << " and " << expr_node->value->name << " are incompatible." << endl;
				exit(1); //For now
			}
		}
	}	
	cout << "Filter: " << endl;
	cout << "Name: " << name << endl;
	
	switch(op)
	{
		case OP_NONE:
					cout << "Operator: OP_NONE " << endl;
					break;
		case OP_EQ: 
					cout << "Operator: OP_EQ " << endl;
					break;
		case OP_NEQ:	
					cout << "Operator: OP_NEQ " << endl;
					break;
		case OP_LT:
					cout << "Operator: OP_LT " << endl;
					break;
		case OP_LTE:
					cout << "Operator: OP_LTE " << endl;
					break;
		case OP_GT:
					cout << "Operator: OP_GT " << endl;
					break;
		case OP_GTE:
					cout << "Operator: OP_GTE " << endl;
					break;
		case OP_PLUS: 
					cout << "Operator: OP_PLUS " << endl;
					break;
		case OP_MINUS: 
					cout << "Operator: OP_MINUS " << endl;
					break;
		case OP_MULTIPLY:
					cout << "Operator: OP_MULTIPLY " << endl;
					break;
		case OP_DIVIDE:
					cout << "Operator: OP_DIVIDE " << endl;
					break;
		case OP_MODULUS:
					cout << "Operator: OP_MODULUS " << endl;
					break;
		case OP_BAND:
					cout << "Operator: OP_BITWISE_AND " << endl;
					break;
		case OP_BOR:
					cout << "Operator: OP_BITWISE_OR " << endl;
					break;
		case OP_XOR:
					cout << "Operator: OP_XOR " << endl;
					break;
		case OP_LS:
					cout << "Operator: OP_LEFT_SHIFT " << endl;
					break;
		case OP_RS:
					cout << "Operator: OP_RIGHT_SHIFT " << endl;
					break;
		case OP_BNOT:
					cout << "Operator: OP_BITWISE_NOT " << endl;
					break;
	}
	
	cout << "Node: " << (char *)node << endl;
	return (void *)name;
}

void *TestFlow::setFilter4(void *node1, MINEOPERATOR op, void *node2, int total, int counter)
{
	cout << "Filter: " << endl;
	cout << "Node: " << (char *)node1 << endl;
	cout << "Operator: " << op << endl;
	cout << "Node: " << (char *)node2 << endl;
	return node1;
}
